package com.sinosoft.cloud.access.net;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.ServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.util.Map;

/**
 * cloud-netty
 *
 * @title: cloud-netty
 * @package: com.sinosoft.cloud.access.net
 * @author: yangming
 * @date: 2017/12/2 上午11:45
 */
@Component
@Order()
@CommonsLog
public class NettyServer {


    private final static String KEY_PORT = "port";


    @Autowired
    private NettyProperties nettyProperties;

    @Autowired
    private EventLoopGroup workerGroup;

    @PostConstruct
    public void initServer() {

        if (nettyProperties == null || nettyProperties.getServers() == null) {
            String errMsg = "必须配置 spring.netty.servers.[accessName]";
            log.error(errMsg);
            throw new RuntimeException(errMsg);
        }

        Map<String, Map> serverInfoMap = nettyProperties.getServers();
        for (String accessName : serverInfoMap.keySet()) {
            Map accessMap = serverInfoMap.get(accessName);
            startAsync(accessName, Integer.parseInt((String) accessMap.get(KEY_PORT)));
        }

    }


    /**
     * 新建执行线程池
     *
     * @return
     */
    protected EventLoopGroup initWorkerGroup() {
        if(workerGroup == null){
            if (nettyProperties != null && nettyProperties.getWorkerThreads() > 0) {
                workerGroup = new NioEventLoopGroup(nettyProperties.getWorkerThreads());
            } else {
                workerGroup = new NioEventLoopGroup();
            }
        }

        return workerGroup;
    }

    protected void applyConnectionOptions(ServerBootstrap bootstrap) {

        if (nettyProperties != null && nettyProperties.getTcpNoDelay() != null) {
            bootstrap.childOption(ChannelOption.TCP_NODELAY, nettyProperties.getTcpNoDelay());
        }
        if (nettyProperties != null && nettyProperties.getTcpSendBufferSize() != null) {
            bootstrap.childOption(ChannelOption.SO_SNDBUF, nettyProperties.getTcpSendBufferSize());
        }
        if (nettyProperties != null && nettyProperties.getTcpReceiveBufferSize() != null) {
            bootstrap.childOption(ChannelOption.SO_RCVBUF, nettyProperties.getTcpReceiveBufferSize());
            bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(nettyProperties.getTcpReceiveBufferSize()));
        }
        if (nettyProperties != null && nettyProperties.getTcpKeepAlive() != null) {
            bootstrap.childOption(ChannelOption.SO_KEEPALIVE, nettyProperties.getTcpKeepAlive());
        }
        if (nettyProperties != null && nettyProperties.getSoLinger() != null) {
            bootstrap.childOption(ChannelOption.SO_LINGER, nettyProperties.getSoLinger());
        }
        if (nettyProperties != null && nettyProperties.getReuseAddress() != null) {
            bootstrap.option(ChannelOption.SO_REUSEADDR, nettyProperties.getReuseAddress());
        }
        if (nettyProperties != null && nettyProperties.getAcceptBackLog() != null) {
            bootstrap.option(ChannelOption.SO_BACKLOG, nettyProperties.getAcceptBackLog());
        }
        if (nettyProperties != null && nettyProperties.getTcpNoDelay() != null) {
            bootstrap.childOption(ChannelOption.TCP_NODELAY, nettyProperties.getTcpNoDelay());
        }
    }

    /**
     * 起步启动netty服务
     *
     * @param accessName
     * @param port
     * @return
     */
    public Future<Void> startAsync(String accessName, int port) {
        log.info("开始启动"+accessName);
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = initWorkerGroup();
        addShutdownHook(bossGroup, workGroup);
        Class<? extends ServerChannel> channelClass = NioServerSocketChannel.class;
        ServerBootstrap b = new ServerBootstrap();

        AccessChannelInitializer accessChannelInitializer = new AccessChannelInitializer();
        accessChannelInitializer.setAccessName(accessName);
        accessChannelInitializer.setPort(port);

        b.group(bossGroup, workGroup)
                .channel(channelClass)
                .childHandler(accessChannelInitializer);
        applyConnectionOptions(b);
        InetSocketAddress addr = new InetSocketAddress(port);
        return b.bind(addr).addListener((FutureListener<Void>) future -> {
            if (future.isSuccess()) {
                log.info("完成"+accessName+"服务启动");
            } else {
                log.error(accessName + " 启动失败! " );
            }
        });
    }

    /**
     * @param boosGroup
     * @param workGroup
     */
    private void addShutdownHook(EventLoopGroup boosGroup, EventLoopGroup workGroup) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            log.info("shutdown NettyServer");
            boosGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }));
    }

}
